﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.Runtime.Serialization;

namespace NuMetaheuristics.Algorithms
{
    /// <summary>
    /// The basic hill-climbing algorithm replaces an existing solution with 
    /// the best nearby solution. Nearby solutions are found by cloning and 
    /// mutating the existing solution. The number of clones is determined by 
    /// the Gradient Samples parameter.
    /// </summary>
    /// <typeparam name="G">The genotype class which inherits from IGenotype.</typeparam>
    [DataContract(Name = "HillClimbingAlgorithmOf{0}", 
        Namespace = "http://numetaheuristics.codeplex.com/schemas")]
    [Export(typeof(IAlgorithm<>))]
    public class HillClimbingAlgorithm<G> : IAlgorithm<G>
        where G : IGenotype
    {
        /// <summary>
        /// Responsible for modifying cloned solutions to test nearby search space.
        /// </summary>
        [DataMember]
        private IUnaryOperator<G> _mutationOperator;

        /// <summary>
        /// The number of nearby solutions to test.
        /// </summary>
        [DataMember]
        private int _gradientSamples;

        /// <summary>
        /// The default, no-argument constructor.
        /// </summary>
        public HillClimbingAlgorithm()
            : base()
        {
        }

        /// <summary>
        /// The complete constructor which initializes all properties.
        /// </summary>
        /// <param name="context">Provide context to the algorithm, so it can 
        /// evaluate candidate solutions.</param>
        /// <param name="iterationsPer">The number of optimization iterations to perform at a time.</param>
        /// <param name="mutator">The operator used to modify a solution, looking for better nearby solutions.</param>
        /// <param name="gradientSamples">The number of nearby solutions to test during each iteration.</param>
        public HillClimbingAlgorithm(IUnaryOperator<G> mutator, int gradientSamples)
        {
            _mutationOperator = mutator;

            if (gradientSamples < 1)
                throw new ArgumentOutOfRangeException();

            _gradientSamples = gradientSamples;
        }

        /// <summary>
        /// Perform optimization using the given candidate solutions.
        /// </summary>
        /// <param name="population">The candidate solutions to be used 
        /// during optimization</param>
        public void operate(ref G operand, IContext<G> context)
        {
            G best = default(G);

            //Steepest ascent hill climb: sample the gradient and pick the best.
            for (int i = 1; i < _gradientSamples; i++)
            {
                G tweaked = (G)operand.Clone();
                _mutationOperator.Operate(ref tweaked);
                context.Evaluate(ref tweaked);

                if (best == null || tweaked.Fitness > best.Fitness)
                {
                    best = tweaked;
                }
            }

            best.CopyTo(operand);
        }


        public void Run(Population<G> population, IContext<G> context, uint iterations)
        {
            for (uint i = 0; i < iterations; i++)
            {
                foreach (G genome in population.Genomes)
                {
                    G operand = genome;
                    operate(ref operand, context);
                }
            }
        }
    }
}
